package cmm.Lexer;

import cmm.Exception.LexerException;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Created by zhangzheyu on 2017/10/10.
 */
public class Lexer {
    public ArrayList<Token> tokens = new ArrayList<Token>();
    private BufferedReader reader;
    private  boolean isNotRead =false;
    private  int CurrentInt;
    private  char ch;
    private int lineNum = 0;
    private boolean isNegative;
    private char preCh;
    private int position = 0;
    private StringBuffer buffer =new StringBuffer();
    public static HashMap<String, TokenType> ReserveWord = new HashMap<>();
    static {
        ReserveWord.put("if",TokenType.IF);
        ReserveWord.put("else",TokenType.ELSE);
        ReserveWord.put("while",TokenType.WHILE);
        ReserveWord.put("read",TokenType.READ);
        ReserveWord.put("write",TokenType.WRITE);
        ReserveWord.put("int",TokenType.INT);
        ReserveWord.put("real",TokenType.REAL);
    }
    //符号表
    public static HashMap<String, TokenType> Operator = new HashMap<>();
    static {
        Operator.put("*",TokenType.MUL );
        Operator.put("/",TokenType.DIV );
        Operator.put("+",TokenType.PLUS );
        Operator.put("-",TokenType.MINUS );
        Operator.put("<",TokenType.LT );
        Operator.put(">",TokenType.GT );
        Operator.put("<=",TokenType.LET );
        Operator.put(">=",TokenType.GET );
        Operator.put("=",TokenType.ASSIGN );
        Operator.put("==",TokenType.EQ );
        Operator.put("<>",TokenType.NEQ );
    }
    public Lexer(BufferedReader reader)throws Exception{
        this.reader =reader;
    }
    public  ArrayList<Token> getTokens(){
        return tokens;
    }
    public void lex()throws Exception{
        lineNum = 1;
        ArrayList<Token> tokenList = new ArrayList<Token>();
        StringBuilder stringBuilder =new StringBuilder();
        readChar();
        while (CurrentInt!=-1){
            if(ch == '\n'|| ch == '\r'||ch == '\t'||ch == ' '){
                readChar();
                continue;
            }
            if (ch == ';'){
                tokenList.add(new Token(TokenType.SEMI,";",lineNum,position));
                readChar();
                continue;
            }else if (ch == ','){
                tokenList.add(new Token(TokenType.COMMA,",",lineNum,position));
                readChar();
                continue;
            }else if (ch == '+'){
                tokenList.add(new Token(TokenType.PLUS,"+",lineNum,position));
                readChar();
                continue;
            }else if (ch == '-'){
                if (isDigit(preCh)||isChar(preCh)){
                    tokenList.add(new Token(TokenType.MINUS,"-",lineNum,position));
                    readChar();
                    continue;
                }
                readChar();
                if (isDigit(ch)){
                    isNegative = true;
                    Token token = readNum();
                    tokenList.add(token);
                }else{
                    tokenList.add(new Token(TokenType.MINUS,"-",lineNum,position));
                }

                //readChar();
                continue;
            }else if (ch == '*'){
                tokenList.add(new Token(TokenType.MUL,"*",lineNum,position));
                readChar();
                continue;
            }else if (ch == '%'){
                tokenList.add(new Token(TokenType.MOD,"%",lineNum,position));
                readChar();
                continue;
            }else if (ch == '('){
                tokenList.add(new Token(TokenType.LPARENT,"(",lineNum,position));
                readChar();
                continue;
            }else if (ch == ')'){
                tokenList.add(new Token(TokenType.RPARENT,")",lineNum,position));
                readChar();
                continue;
            }else if (ch == '['){
                tokenList.add(new Token(TokenType.LBRACKET,"[",lineNum,position));
                readChar();
                continue;
            }else if (ch == ']'){
                tokenList.add(new Token(TokenType.RBRACKET,"]",lineNum,position));
                readChar();
                continue;
            }else if (ch == '{'){
                tokenList.add(new Token(TokenType.LBRACE,"{",lineNum,position));
                readChar();
                continue;
            }else if (ch == '}'){
                tokenList.add(new Token(TokenType.RBRACE,"}",lineNum,position));
                readChar();
                continue;
            }else if (ch == '/'){
                readChar();
                if (ch == '*'){
                    readChar();
                    while(CurrentInt !=-1){
                        if (ch == '*'){
                            readChar();;
                            if (ch == '/'){
                                readChar();
                                break;
                            }
                        }else{
                            readChar();
                        }
                    }
                    continue;
                }else if (ch == '/'){
                    while(ch != '\n'){
                        readChar();
                    }
                    continue;
                }else{
                    tokenList.add(new Token(TokenType.DIV,"/",lineNum,position));
                    continue;
                }
            }else if (ch == '!'){
                readChar();
                if (ch == '='){
                    tokenList.add(new Token(TokenType.NEQ,"!=",lineNum,position));
                    readChar();
                }else{
                    throw new LexerException("line: "+ lineNum + "position: "+ position+"after '!' must be an '='");
                }
                continue;
            }else if (ch == '='){
                readChar();
                if (ch == '='){
                    tokenList.add(new Token(TokenType.EQ,"==",lineNum,position));
                    readChar();
                }else{
                    tokenList.add(new Token(TokenType.ASSIGN,"=",lineNum,position));
                }
                continue;
            }else if (ch == '>'){
                readChar();
                if (ch == '='){
                    tokenList.add(new Token(TokenType.GET,">=",lineNum,position));
                    readChar();
                }else{
                    tokenList.add(new Token(TokenType.GT,">",lineNum,position));
                }
                continue;
            }else if (ch == '<'){
                readChar();
                if (ch == '='){
                    tokenList.add(new Token(TokenType.LET,"<=",lineNum,position));
                    readChar();
                }else{
                    tokenList.add(new Token(TokenType.LT,"<",lineNum,position));
                }
                continue;
            }else if (isDigit(ch)){
                Token token = readNum();
                tokenList.add(token);
                //readChar();
                continue;
            }else if (isChar(ch)){
                Token token = readCh();
                tokenList.add(token);
                //readChar();
                continue;
            }
            readChar();
        }
        tokens = tokenList;

    }
    public Token readNum()throws Exception{
        StringBuilder stringBuilder = new StringBuilder();
        if (isNegative){
            stringBuilder.append('-');
            isNegative = false;
        }
        stringBuilder.append(ch);
        boolean isReal = false;
        while (true){
            readChar();
            if (ch == '.'||isDigit(ch)){
                stringBuilder.append(ch);
                isReal = true;
                continue;
            }else if (!isDigit(ch)){
                if (isChar(ch)){
                    throw new LexerException("The number at line:" + lineNum + " position:"+position+" is followed by illegal char");
                }else{
                    break;
                }
            }
        }
        if(isReal){
            return new Token(TokenType.LITERAL_REAL,stringBuilder.toString(),lineNum,position);
        }
        return new Token(TokenType.LITERAL_INT,stringBuilder.toString(),lineNum,position);

    }
    public Token readCh() throws Exception{
        StringBuilder stringBuilder = new StringBuilder();
        while ((ch >= 'a' && ch <= 'z')
                || (ch >= 'A' && ch <= 'Z')
                || ch == '_'
                || (ch >= '0' && ch <= '9'))
        {
            stringBuilder.append(ch);
            readChar();
            position--;
        }
        String value = stringBuilder.toString();
        Token token;
        if (value.equals("if")){
            token =new Token(TokenType.IF,value,lineNum,position);
        }else if (value.equals("else")){
            token =new Token(TokenType.ELSE,value,lineNum,position);
        }else if(value.equals("while")){
            token = new Token(TokenType.WHILE,value,lineNum,position);
        }else if (value.equals("read")){
            token = new Token(TokenType.READ,value,lineNum,position);
        }else if(value.equals("write")){
            token = new Token(TokenType.WRITE,value,lineNum,position);
        }else if (value.equals("int")){
            token = new Token(TokenType.INT,value,lineNum,position);
        }else if (value.equals("real")){
            token =new Token(TokenType.REAL,value,lineNum,position);
        }else if (value.equals("true")){
            token =new Token(TokenType.TRUE,value,lineNum,position);
        }else if (value.equals("false")){
            token =new Token(TokenType.FALSE,value,lineNum,position);
        }else if (value.equals("break")){
            token =new Token(TokenType.BREAK,value,lineNum,position);
        }else if (value.equals("for")){
            token =new Token(TokenType.FOR,value,lineNum,position);
        }else if (value.equals("return")){
            token =new Token(TokenType.RETURN,value,lineNum,position);
        }else {
            token = new Token(TokenType.ID,value,lineNum,position);
        }
        position+= stringBuilder.length();
        return token;
    }
    public boolean isDigit(char c){
        return c >='0'&&c<='9';
    }
    public boolean isChar(char c){
        return (c >= 'a' && c <= 'z') || c == '_'||(c>='A'&&c<='Z');
    }
    public  void readChar()throws Exception{
        if (ch != ' '){
            preCh = ch;
        }
        ch = (char)(CurrentInt = reader.read());
        position ++;
        if(ch == '\n'){
            lineNum++;
            position = 0;
        }
    }
    public StringBuffer formStringBuffer(){
        for (Token token : this.getTokens()){
            buffer.append(token.getType() +" "+ token.getValue());
            buffer.append("\n");
        }
        return  buffer;
    }
}
